热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Linux|多线程与fork

经由fork()创建的子进程,其中只有一个线程。子进程里仅存的线程,对应着主进程里调用fork()函数的线程。此外,主进程的整个虚存空间都

经由 fork() 创建的子进程,其中只有一个线程。子进程里仅存的线程,对应着主进程里调用 fork() 函数的线程。此外,主进程的整个虚存空间都被复制到了子进程。因而,包括互斥锁、条件变量、其余线程的内部对象等,都保持着原样。由此引发的问题,可以考虑用 pthread_atfork() 函数解决。
参考自:https://liam.page/2017/01/17/fork-safe/

1.线程内fork产生的子进程,只会执行该线程部分(主进程不会向下执行)

#include
#include
#include
#include
#include
#include
/* 功能说明:* 主函数 ---- ------main:主线程---> 打印:main:主线程id=..* 工作线程 --fork() ---fun:父进程---> 打印: fun:父进程id=.. * |---fun:子进程---> 打印: fun: 子进程id=..*/void *Thread_fun()
{pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}else{for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);exit(0);
}

在这里插入图片描述
fork产生的子进程中只有一个线程。

执行结果&#xff1a;
main::主线程 【1】:id &#61; 74226
fun::父进程 【1】:id &#61; 74226
fun::子进程 【1】:id &#61; 74228
fun::父进程 【2】:id &#61; 74226
main::主线程 【2】:id &#61; 74226
fun::子进程 【2】:id &#61; 74228
fun::父进程 【3】:id &#61; 74226
main::主线程 【3】:id &#61; 74226
fun::子进程 【3】:id &#61; 74228
main is stop …

2.尝试加锁控制执行状态

如果我们把输出屏幕看成一块共享资源的话&#xff0c;我们想要输出结果为&#xff0c;先让main中的打印完成&#xff0c;在打印线程内的。则我们需要加锁完成。

2.1 fork复制了main中锁状态&#xff0c;则线程内的子进程部分加锁。效果不理想

把临界区代码加锁。&#xff08;需要注意的是&#xff0c;fork会复制父进程加锁状态&#xff09;&#xff0c;如果我们代码这样写。

include <stdio.h>
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 加锁&#xff1a;保证先让main中的打印先完成*/void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{// 注意&#xff1a;这里没加锁&#xff0c;因为子进程复制的父进程此时是处于加锁状态的&#xff0c;如果这里加锁会产生死锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

执行结果&#xff1a;
main::主线程 【m 1】:id &#61; 75080
main::主线程 【m 2】:id &#61; 75080
fun::子进程 【f_c1】:id &#61; 75082
main::主线程 【m 3】:id &#61; 75080
fun::子进程 【f_c2】:id &#61; 75082
fun::子进程 【f_c3】:id &#61; 75082
main is stop …
fun::父进程 【f_f1】:id &#61; 75080
fun::父进程 【f_f2】:id &#61; 75080
fun::父进程 【f_f3】:id &#61; 75080

可以看到&#xff0c;在父进程中&#xff0c;遵循先答应main的内容&#xff0c;在打印线程函数。&#xff08;main is stop … 之后才执行fun::父进程&#xff09;。但是由于子进程中临界区没有加锁&#xff0c;子进程的输出不受限制&#xff0c;干扰了主进程中的main的输出。

2.1 fork复制了main中锁状态&#xff0c;则线程内的子进程部分加锁。产生死锁&#xff0c;子进程一直阻塞

如果我们给子进程中的临界区加锁又会产生另一种问题。&#xff08;死锁&#xff09;以下代码仅修改了子进程的临界区进行加锁操作。

#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
/* 功能说明&#xff1a;* 主函数 ---- ------main:主线程---> 打印&#xff1a;main&#xff1a;主线程id&#61;..* 工作线程 --fork() ---fun&#xff1a;父进程---> 打印&#xff1a; fun&#xff1a;父进程id&#61;.. * |---fun&#xff1a;子进程---> 打印&#xff1a; fun: 子进程id&#61;..** 在工作线程中fork&#xff0c;产生的子进程&#xff0c;只会执行工作线程内的代码。但子进程会复制父进程的状态&#xff0c;如加锁。*** 加锁&#xff1a;保证先让main中的打印先完成* 问题&#xff1a;fork会复制父进程中的加锁状态&#xff0c;即在工作线程中&#xff0c;子进程已经是处于加锁状态的&#xff0c;再次加锁无法完成&#xff0c;则产生死锁。*/// 注意&#xff1a;以下代码子进程会产生死锁
void *Thread_fun()
{sleep(1); // 保证让主进程先获得锁pid_t pid &#61; fork();if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{/* 这里产生死锁&#xff0c;复制的父进程中加锁状态,* 但是线程内使用fork不会再执行父进程中的代码&#xff0c;因此..*/pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75247
main::主线程 【m 2】:id &#61; 75247
main::主线程 【m 3】:id &#61; 75247
main is stop …
fun::父进程 【f_f1】:id &#61; 75247
fun::父进程 【f_f2】:id &#61; 75247
fun::父进程 【f_f3】:id &#61; 75247

2.3.分析阻塞原因

可以看到子进程中&#xff0c;并没有执行打印操作。使用ps查看&#xff0c;发现有进程还未退出&#xff0c;正是我们的子进程。
在这里插入图片描述
父进程执行过程大致如下&#xff1a;
在这里插入图片描述
子进程中&#xff0c;因为工作线程以外的部分不继续执行了&#xff0c;则工作线程一直处于阻塞状态。
在这里插入图片描述

2.4 对fork加锁&#xff08;试探锁&#xff0c;试探main中是否已经解锁&#xff09;

解决方案&#xff1a;
确保fork()时&#xff0c;父进程是没有加锁的。因为本程序需要实现先打印main的内容&#xff0c;因此我们可以这样做&#x1f447;&#xff0c;确保main中先打印并且不会占用占用锁资源。

// 尝试加锁&#xff0c;如果可以加锁&#xff0c;则证明父进程没有占用锁资源
pid_t pid &#61; fork();
// 解锁

代码如下&#xff1a;

#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁void *Thread_fun()
{pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证临界区锁以释放&#xff0c;不会被fork复制到子进程中阻塞临界区pid_t pid &#61; fork();pthread_mutex_unlock(&mutex); // 解锁if(pid !&#61; 0) // 父进程&#xff1a;pid等于子进程id{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::父进程 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}else{pthread_mutex_lock(&mutex); // 加锁for(int i &#61; 0; i < 3; i&#43;&#43;){printf(" fun::子进程 【f_c%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&mutex); // 解锁}
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_t id; // 创建线程并调用工作线程int res &#61; pthread_create(&id,NULL,Thread_fun,NULL);assert(res &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功pthread_mutex_lock(&mutex); // 加锁&#xff0c;保证主线程先执行输出操作for(int i &#61; 0; i < 3; i&#43;&#43;){printf("main::主线程 【m %d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&mutex); // 解锁&#xff0c;释放输出缓冲区资源// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);exit(0);
}

输出结果&#xff1a;
main::主线程 【m 1】:id &#61; 75807
main::主线程 【m 2】:id &#61; 75807
main::主线程 【m 3】:id &#61; 75807
main is stop …
fun::父进程 【f_f1】:id &#61; 75807
fun::子进程 【f_c1】:id &#61; 75811
fun::子进程 【f_c2】:id &#61; 75811
fun::父进程 【f_f2】:id &#61; 75807
fun::子进程 【f_c3】:id &#61; 75811
fun::父进程 【f_f3】:id &#61; 75807

3.系统提供的解决方案 pthread_atfork

如果我们有很多不同的锁&#xff0c;按照我们之前的解决方案&#xff0c;我们需要在 fork 之前进行 n 多次的试探动作&#xff0c;不仅麻烦还极容易引起死锁。而系统为我们提供了一个函数可以解决这个问题。

#include int pthread_atfork (void (*prepare) (void), void (*parent) (void), void (*child) (void));#include
#include
#include
#include
#include
#include pthread_mutex_t mutex; // 创建互斥锁
pthread_mutex_t lock; // 创建互斥锁void prepare() {pthread_mutex_lock(&mutex); // 加锁printf("prepare: 加锁 ...\n");
}void parent() {pthread_mutex_unlock(&mutex);printf("parent : 父进程解锁 ...\n");
}void child() {pthread_mutex_unlock(&mutex);printf("child : 子进程解锁 ...\n");
}void *Thread_fun(void*arg)
{sleep(1);pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 5; i&#43;&#43;){printf(" fun::%s 【f_f%d】:id &#61; %d\n",(char*)arg,i&#43;1,getpid());sleep(1);}pthread_mutex_unlock(&lock); // 解锁
}int main()
{pthread_mutex_init(&mutex,NULL);pthread_mutex_init(&lock,NULL);pthread_t id; // 创建线程并调用工作线程// 注册forkint res &#61; pthread_atfork(prepare, parent, child);assert(res &#61;&#61; 0);char buff[] &#61; "父进程";int ret &#61; pthread_create(&id,NULL,Thread_fun,(void*)buff);assert(ret &#61;&#61; 0); // 返回值为0&#xff0c;表示创建成功/* fork调用前调用preoarefork调用完&#xff0c;在调用child和parent*/pid_t pid &#61; fork();/*在父进程中&#xff08;当前进程中&#xff09;调用fork。经由 fork() 创建的子进程&#xff0c;其中只有一个线程&#xff08;调用fork的线程&#xff0c;即主线程&#xff09;因此&#xff0c;fork产生的子进程中&#xff0c;不会执行Thread_fun()函数&#xff0c;则我们可以在父进程&#xff08;当前进程中手动调用&#xff09;*/if(pid &#61;&#61; 0) // 子进程{sleep(1); // 保证main中父进程先执行// 注&#xff1a;这里的子进程与父进程是两个进程&#xff0c;不共享锁。同步需要设置管道或者信号量等 // ********strcpy(buff,"子进程");Thread_fun((void*)buff);}else // 父进程{pthread_mutex_lock(&lock); // 加锁for(int i &#61; 0; i < 10; i&#43;&#43;){printf("main::主函数 【f_f%d】:id &#61; %d\n",i&#43;1,getpid());sleep(1);}printf("main is stop ... \n");pthread_mutex_unlock(&lock); // 解锁}// 等待工作线程结束&#xff0c;在此阻塞pthread_join(id,NULL);// 销毁互斥锁pthread_mutex_destroy(&mutex);pthread_mutex_destroy(&lock);exit(0);
}

输出&#xff1a;
父进程&#xff1a;先输出 main中的&#xff0c;再输出工作线程中的
子进程&#xff1a;只有一个线程&#xff0c;在main中&#xff0c;通过手动调用执行工作函数。由于进程间全局变量&#xff08;我们定义的锁&#xff09;不共享&#xff0c;所以子进程在输出时不受锁影响。如有需要加入型号量等。。


推荐阅读
  • 在iOS开发中,多线程技术的应用非常广泛,能够高效地执行多个调度任务。本文将重点介绍GCD(Grand Central Dispatch)在多线程开发中的应用,包括其函数和队列的实现细节。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • Java连接MySQL数据库的方法及测试示例
    本文详细介绍了如何安装MySQL数据库,并通过Java编程语言实现与MySQL数据库的连接,包括环境搭建、数据库创建以及简单的查询操作。 ... [详细]
  • 深入理解Java SE 8新特性:Lambda表达式与函数式编程
    本文作为‘Java SE 8新特性概览’系列的一部分,将详细探讨Lambda表达式。通过多种示例,我们将展示Lambda表达式的不同应用场景,并解释编译器如何处理这些表达式。 ... [详细]
  • JUC并发编程——线程的基本方法使用
    目录一、线程名称设置和获取二、线程的sleep()三、线程的interrupt四、join()五、yield()六、wait(),notify(),notifyAll( ... [详细]
  • 本文将探讨如何在 Struts2 中使用 ActionContext 和 ServletActionContext 来获取请求参数和会话信息,同时解释它们的内部机制和最佳实践。 ... [详细]
  • 关于进程的复习:#管道#数据的共享Managerdictlist#进程池#cpu个数1#retmap(func,iterable)#异步自带close和join#所有 ... [详细]
  • 大华股份2013届校园招聘软件算法类试题D卷
    一、填空题(共17题,每题3分,总共51分)1.设有inta5,*b,**c,执行语句c&b,b&a后,**c的值为________答:5 ... [详细]
  • 本文将深入探讨 iOS 中的 Grand Central Dispatch (GCD),并介绍如何利用 GCD 进行高效多线程编程。如果你对线程的基本概念还不熟悉,建议先阅读相关基础资料。 ... [详细]
  • Java高并发与多线程(二):线程的实现方式详解
    本文将深入探讨Java中线程的三种主要实现方式,包括继承Thread类、实现Runnable接口和实现Callable接口,并分析它们之间的异同及其应用场景。 ... [详细]
  • 在 Java 中,`join()` 方法用于使当前线程暂停,直到指定的线程执行完毕后再继续执行。此外,`join(long millis)` 方法允许当前线程在指定的毫秒数后继续执行。 ... [详细]
  • 在 Linux 环境下,多线程编程是实现高效并发处理的重要技术。本文通过具体的实战案例,详细分析了多线程编程的关键技术和常见问题。文章首先介绍了多线程的基本概念和创建方法,然后通过实例代码展示了如何使用 pthreads 库进行线程同步和通信。此外,还探讨了多线程程序中的性能优化技巧和调试方法,为开发者提供了宝贵的实践经验。 ... [详细]
  • 在MFC框架中,存在多个全局函数,用于在不同对象间获取信息或创建新对象。其中,`afxGetApp`函数尤为关键,它能够帮助开发者轻松获取当前应用程序的实例指针。本文将详细解析`afxGetApp`函数的内部机制及其在MFC应用程序中的具体应用场景,探讨其在提升代码可维护性和灵活性方面的优势。此外,还将介绍其他常用全局函数如`AfxWinInit()`和`AfxBeginThread()`的功能和使用方法,为开发者提供全面的参考。 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 本文介绍了如何使用 Python 的 Pyglet 库加载并显示图像。Pyglet 是一个用于开发图形用户界面应用的强大工具,特别适用于游戏和多媒体项目。 ... [详细]
author-avatar
手机用户2502885897
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有